﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ServiceModel;
using System.Collections;
using System.Windows.Threading;
using JpLabs.CustomEvents;
using System.Threading.Tasks;
using System.Threading;
using DistributedGame.Service;
using DistributedGame.Server;

namespace DistributedGame
{
	public static class GameClientCallbackExt
	{
		//public static void ReceiveAsync(this IGameClientCallback callback, string message)
		//{
		//    var dlg = (Action<string>)callback.Receive;
		//    dlg.BeginInvoke(message, null, null);
		//}

		public static object InvokeIfNeeded(this Dispatcher dispatcher, Delegate dlg, params object[] args)
		{
			if (!dispatcher.CheckAccess()) return dispatcher.Invoke(dlg, DispatcherPriority.Send, args);
			
			return dlg.DynamicInvoke(args);
		}

		//public static object BeginInvokeIfNeeded(this Dispatcher dispatcher, Delegate dlg, params object[] args)
		//{
		//    return dispatcher.BeginInvoke(dlg, args);
		//}
	}
	
	/// <summary>
	/// Interaction logic for HostPage.xaml
	/// </summary>
	[ServiceBehavior(
		InstanceContextMode=InstanceContextMode.Single
		,ConcurrencyMode=ConcurrencyMode.Reentrant
		,UseSynchronizationContext = false
		//,IncludeExceptionDetailInFaults = true
		)
	]
	public partial class HostPage : Page, IDisposable//, IGameHost
	{
		public Uri HostAddress { get; set; }
		
		public GameServer GameServer { get; private set; }
		
		public HostPage()
		{
			InitializeComponent();

			txtChatArea.Document.LineHeight = 1;

			//this.NavigationService.Navigating += CustomEvent.ToWeak<NavigatingCancelEventHandler>(NavigationService_Navigating);
			//NavigationService.GetNavigationService(this).Navigating += CustomEvent.ToWeak<NavigatingCancelEventHandler>(NavigationService_Navigating);

			this.Dispatcher.UnhandledException += CustomEvent.ToWeak<DispatcherUnhandledExceptionEventHandler>(Dispatcher_UnhandledException);
		}

		private void NavigationService_Navigating(object sender, NavigatingCancelEventArgs e)
		{
		    if (this.ServerState == CommunicationState.Opened) {
		        var response = MessageBox.Show(
		            Window.GetWindow(this),
		            "This action will close the server. Are you sure you want that?",
		            "Close Server",
		            MessageBoxButton.YesNo
		        );

		        if (response == MessageBoxResult.No) {
		            e.Cancel = true;
		            return;
		        }
		    }

		    this.Dispose();
		}

		public CommunicationState ServerState
		{
			get {
				var gameServer = this.GameServer;
				if (gameServer == null) return CommunicationState.Closed;
				return gameServer.State;
			}
		}

		private void Dispatcher_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
		{
			try
			{
				AddChatText(e.Exception.ToString());
				e.Handled = true;
			}
			catch {}
		}

		private void txtChat_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.Key == Key.Enter) {
				string text = txtChat.Text;
				txtChat.Text = null;
				
				TextEntered(text);
			}
		}

		private void TextEntered(string text)
		{
			//text = MessageFormatter.HostMessage(text, txtName.Text);

			var msg = new ClientMessage(
				new ClientConnectionInfo() {
					SessionId = ClientConnectionInfo.HOST_ID,
					Nickname = txtName.Text
				},
				text
			);
			
			if (this.GameServer != null && GameServer.State == CommunicationState.Opened) {
				this.GameServer.GameHost.BroadcastMessage(msg, true);
			}
			
			//AddChatText(MessageFormatter.Message(msg));
		}
		
		private void AddChatText(string text)
		{
			if (!this.Dispatcher.CheckAccess()) {
				this.Dispatcher.Invoke((Action<string>)this.AddChatText, text);
				return;
			}

			txtChatArea.AppendText(text + Environment.NewLine);
		}

		private void btnStartStop_Click(object sender, RoutedEventArgs e)
		{
			var state = this.ServerState;
			switch (state)
			{
				case CommunicationState.Closed: StartServer(this.HostAddress); break;
				case CommunicationState.Created: break;
				//case CommunicationState.Opened:
				default: StopServer(); break;
			}

			UpdateUI();
		}

		private void StartServer(Uri address)
		{
			GameServer = new GameServer();

			AddChatText("Starting server...");

			Task.Factory.StartNew(
				() => this.GameServer.StartServer(address)
			)
			.ContinueWith(
				t => {
					if (t.IsFaulted) {
						AddChatText("Error starting server: " + t.Exception.Message);
					} else {
						AddChatText("Server open at " + address.ToString());

						AttachToHost(this.GameServer.GameHost);
					}
					UpdateUI();
				}
			);
		}

		private void StopServer()
		{
			//AddChatText("Stopping server...");

			Dispose();

			AddChatText("Server stopped");
		}

		public void Dispose()
		{
			var gameServer = this.GameServer;

			if (gameServer != null) gameServer.Dispose();

			this.GameServer = null;
		}

		private void UpdateUI()
		{
			if (!this.Dispatcher.CheckAccess()) {
				this.Dispatcher.Invoke((Action)this.UpdateUI);
				return;
			}

			var state = this.ServerState;

			txtName.IsReadOnly = true;
			btnStartStop.IsEnabled = false;

			//ICommand

			switch (state)
			{
				case CommunicationState.Closed: {
					btnStartStop.Content = "Start Server";
					txtName.IsReadOnly = false;
					btnStartStop.IsEnabled = true;
				} break;

				case CommunicationState.Created:
				case CommunicationState.Opening: {
					btnStartStop.Content = "Starting...";
				} break;

				case CommunicationState.Opened: {
					btnStartStop.Content = "Stop Server";
					btnStartStop.IsEnabled = true;
				} break;

				case CommunicationState.Faulted: {
					btnStartStop.Content = "Faulted - Dispose";
					btnStartStop.IsEnabled = true;
				} break;

				default: {
					btnStartStop.Content = state.ToString();
				} break;
			}
		}

		private void UpdatePlayerList()
		{
			UpdatePlayerList(GameServer.GameHost.GetClientList().Select( c => c.Info ));
		}

		private void UpdatePlayerList(IEnumerable<ClientConnectionInfo> players)
		{
			lstPlayerList.ItemsSource = players.Select( p => p.Nickname ).ToArray();
		}

		private void AttachToHost(GameHost host)
		{
			host.ClientConnected		+= CustomEvent.ToWeakSynced<ClientSessionEventArgs>(this, GameHost_ClientConnected);
			host.ClientDisconnected		+= CustomEvent.ToWeakSynced<ClientSessionEventArgs>(this, GameHost_ClientDisconnected);
			host.ClientChannelClosed	+= CustomEvent.ToWeakSynced<ClientSessionEventArgs>(this, GameHost_ClientChannelClosed);
			host.ClientChannelFaulted	+= CustomEvent.ToWeakSynced<ClientSessionEventArgs>(this, GameHost_ClientChannelFaulted);

			host.MessageReceived		+= CustomEvent.ToWeakSynced<RemoteMessageEventArgs>(this, GameHost_MessageReceived);
		}


		void GameHost_ClientConnected(object sender, ClientSessionEventArgs e)
		{
			UpdatePlayerList();
		}

		void GameHost_ClientDisconnected(object sender, ClientSessionEventArgs e)
		{
			UpdatePlayerList();
		}

		void GameHost_ClientChannelClosed(object sender, ClientSessionEventArgs e)
		{
			this.GameServer.GameHost.BroadcastMessage(new RemoteMessage(string.Concat(e.Client.Info.Nickname + " left!")), true);

			UpdatePlayerList();
		}

		void GameHost_ClientChannelFaulted(object sender, ClientSessionEventArgs e)
		{
			this.GameServer.GameHost.BroadcastMessage(new RemoteMessage(string.Concat(e.Client.Info.Nickname + " faulted?!")), true);

			UpdatePlayerList();
		}

		void GameHost_MessageReceived(object sender, RemoteMessageEventArgs e)
		{
			this.AddChatText(MessageFormatter.Message(e.Message));
		}


		//ClientConnectionInfo IGameHost.Connect(ClientConnectRequest request)
		//{
		//    var client = this.GameServer.GameHost.ConnectClient(request);

		//    //System.Diagnostics.Debug.Write("* Server connect: " + handle.Info.Nick);
		//    //UpdatePlayerList(GameServer.ConnectedClients.Values.Select( c => c.Info ));
		//    //this.Dispatcher.InvokeIfNeeded(
		//    //    (Action<IEnumerable<PlayerInfo>>)UpdatePlayerList, GameServer.ConnectedClients.Values.Select( c => c.Info )
		//    //);
		//    //this.Dispatcher.Invoke(
		//    //    (Delegate)(Action<IEnumerable<PlayerInfo>>)UpdatePlayerList,
		//    //    GameServer.ConnectedClients.Values.Select( c => c.Info ).ToArray()
		//    //);

			
		//    //TODO: The following lines cause a deadlock. Avoid it
		//    //BroadcastMessage(string.Format("* {0} has joined", handle.Info.Nick));
		//    //handle.Callback.ReceiveAsync("* Welcome!");

		//    var channel = OperationContext.Current.Channel;

		//    string newPlayersNickname = client.Info.Nickname;
		//    OperationContext.Current.OperationCompleted += CustomEvent.ToSynced(this,
		//        (sender, e) => {
		//            //throw new InvalidOperationException("debug");

		//            UpdatePlayerList();

		//            var joinedMsg = new RemoteMessage(MessageFormatter.ClientJoinedMessage(newPlayersNickname));

		//            this.AddChatText(MessageFormatter.Message(joinedMsg));

		//            Parallel.Invoke(
		//                () => client.Callback.Receive(new RemoteMessage(MessageFormatter.WelcomeMessage(newPlayersNickname))),
		//                () => this.GameServer.GameHost.BroadcastMessage(joinedMsg)
		//            );
		//        }
		//    );

		//    OperationContext.Current.Channel.Closed += CustomEvent.ToSynced(this,
		//        (sender, e) => {
		//            TextEntered(newPlayersNickname + " left!");
		//            //this.Dispatcher.BeginInvoke((Action<string>)TextEntered, newPlayersNick + " left!");
		//        }
		//    );

		//    //channel.Extensions.Add(IExtension<IContextChannel>

		//    OperationContext.Current.Channel.Faulted += CustomEvent.ToSynced(this,
		//        (sender, e) => {
		//            //channel.Extensions

		//            //var senderAsChannel = sender as IContextChannel;
		//            //var exception1 = channel.GetProperty<Exception>();
		//            //var exception2 = senderAsChannel.GetProperty<Exception>();

		//            TextEntered(newPlayersNickname + " faulted?!");
		//            //this.Dispatcher.BeginInvoke((Action<string>)TextEntered, newPlayersNick + " faulted?!");

		//            //System.ServiceModel.Dispatcher.ChannelDispatcher

		//            //http://stackoverflow.com/questions/381345/in-wcf-in-the-faulted-event-how-do-i-get-the-exception-details

		//            //Extending WCF: http://msdn.microsoft.com/en-us/library/ms733848.aspx

		//            //http://www.codeproject.com/KB/WCF/WCFErrorHandling.aspx
		//        }
		//    );
						
		//    return client.Info;
		//}

		//void IGameHost.Say(string messageText)
		//{
		//    var client = this.GameServer.GameHost.GetCurrentClient();
		//    if (client == null) throw new InvalidOperationException("Client is not authenticated");
			
		//    //TODO: Parse messages
		//    //www.ircbeginner.com/ircinfo/m-commands.htm
		//    //www.ircbeginner.com/ircinfo/ircc-commands.html

		//    //TODO: broadcast message at OperationContext.Current.OperationCompleted
			
		//    //messageText = MessageFormatter.ClientMessage(messageText, client.Info.Nickname);
		//    //this.Dispatcher.InvokeIfNeeded( (Action<string>)AddChatText, messageText );
		//    //this.GameServer.GameHost.BroadcastMessage(messageText);
		//}

		//void IGameHost.Disconnect()
		//{
		//    var client = this.GameServer.GameHost.GetCurrentClient();
		//    if (client == null) throw new InvalidOperationException("Client is not authenticated");

		//    this.GameServer.GameHost.DisconnectClient(client);

		//    OperationContext.Current.OperationCompleted += CustomEvent.ToSynced(this,
		//        (sender, e) => {
		//            UpdatePlayerList();

		//            string text = MessageFormatter.ClientIsDisconnectingMessage(client.Info.Nickname);
		//            AddChatText(text);
		//            //this.GameServer.GameHost.BroadcastMessage(text);
		//        }
		//    );
		//}
	}
}
